/*jshint camelcase:false,newcap:false,forin:false*/
/*global Util*/
/**
 * @license Copyright (c) 2013 Eugene Kuzmenko
 * MIT Licensed
 */
(function () {
  'use strict';
  var init = false, store_in = '_public', slice = Array.prototype.slice,
    listeners = {}, classes = {_public: {}, _protected: {}, _private: {}};
  function ArgumentBag(_protected, _private, args) {
    this._protected = _protected;
    this._private = _private;
    this.args = args;
  }
  function Base() {}
  Base.prototype.on = function (n, fn, once) {
    var me = this;
    if (!Util.isFunction(fn)) {
      return me;
    }
    fn.once = !!once;
    me.events || (me.events = {});
    me.events[n] || (me.events[n] = []);
    if (-1 === me.events[n].indexOf(fn)) {
      me.events[n].push(fn);
    }
    return me;
  };
  Base.prototype.un = function (n, fn) {
    var i;
    if (!this.events) {
      return this;
    }
    switch (arguments.length) {
    case 0:
      delete this.events;
      break;
    case 1:
      delete this.events[n];
      break;
    default:
      i = this.events[n].indexOf(fn);
      if (this.events[n] && (-1 !== i)) {
        this.events[n].splice(i, 1);
      }
    }
    return this;
  };
  Base.prototype.fire = function () {
    var me = this, args = slice.call(arguments), n = args.shift();
    if (!me.events || !me.events[n]) {
      return me;
    }
    Util.each(me.events[n], function (v, i) {
      v.apply(me, args);
      v.once && me.events[n].splice(i, 1);
    });
    return me;
  };
  window.Class = {};
  window.Class.define = function (n, o) {
    var _public, _protected, _private, parent, defined_fields = [], prototype,
      store, extend;
    o || (o = {});
    _public = o._public || {};
    _protected = o._protected || {};
    _private = o._private || {};
    extend = o.extend || _public.extend;
    Util.each([_public, _protected, _private], function (v) {
      Util.each(v, function (w, j) {
        if (-1 !== defined_fields.indexOf(j)) {
          throw 'FatalError: property or method "' + j +
            '" is defined more than once';
        }
        defined_fields.push(j);
      });
    });
    listeners[n] = {};
    delete o._public;
    delete o._protected;
    delete o._private;
    if (extend) {
      if (!classes[store_in][extend]) {
        throw 'FatalError: class "' + extend + '" is not defined';
      }
      _protected.extend = extend;
      _private.extend = extend;
      parent = classes[store_in][extend];
      Util.each(listeners[extend], function (v, i) {
        listeners[n][i] || (listeners[n][i] = []);
        Util.each(v, function (evt) {
          listeners[n][i].push(evt);
        });
      });
    } else {
      parent = Base;
    }
    if ('_public' === store_in) {
      store = store_in;
      store_in = '_protected';
      window.Class.define(n, _protected);
      store_in = '_private';
      window.Class.define(n, _private);
      store_in = store;
    }
    Util.each(o.listeners || {}, function (v, i) {
      (listeners[n][i] || (listeners[n][i] = [])).push(v);
    });
    delete o.listeners;
    Util.each(o, function (v, i) {
      _public[i] = v;
    });
    init = true;
    prototype = new parent();
    prototype.type = n;
    init = false;
    Util.each(_public, function (v, i) {
      //noinspection JSCheckFunctionSignatures
      prototype[i] = Util.isFunction(v) ? (function (name, fn, _super) {
        return function (bag) {
          //noinspection SpellCheckingInspection
          var me = this, prot = {}, priv = {},
            parent = me.parent, callParent = me.callParent, response,
            _private_super = Util.getPrototype(bag._private) || {}, i;
          me.callParent = function () {
            var args = slice.call(arguments), name = args.shift(),
              _protected_super = Util.getPrototype(bag._protected) || {};
            return (_super[name] || _protected_super[name] || function () {})
              .apply(me, [_protected_super, _private_super, args]);
          };
          me.parent = function () {
            var a;
            me.callParent.apply(me, ['init'].concat(slice.call(
              ((a = arguments.length ? arguments : bag.args) && a.length) ? a :
                []
            )));
          };
          for (i in bag._protected) {
            //noinspection JSUnfilteredForInLoop
            if (Util.isUndefined(me[i])) {
              //noinspection JSUnfilteredForInLoop
              prot[i] = me[i];
              //noinspection JSUnfilteredForInLoop
              (me[i] === bag._protected[i]) || (i === 'constructor') ||
                (me[i] = bag._protected[i]);
            }
          }
          Util.each([_private_super, bag._private], function (v) {
            Util.each(v, function (w, j) {
              if (Util.isUndefined(me[j])) {
                priv[j] = me[j];
                (me[j] === w) || (j === 'constructor') || (me[j] = w);
              }
            });
          });
          response = fn.apply(me, bag.args);
          if (Util.isFunction(parent)) {
            me.parent = parent;
          } else {
            delete me.parent;
          }
          if (Util.isFunction(callParent)) {
            me.callParent = callParent;
          } else {
            delete me.callParent;
          }
          Util.each([[prot, bag._protected], [priv, bag._private]],
              function (v) {
            Util.each(v[0], function (w, j) {
              (function apply(i, me, pr) {
                if ((me[i] !== pr[i]) && (i !== 'constructor')) {
                  if (Util.isIterable(me[i]) &&
                      Util.isIterable(pr[i])) {
                    Util.each(me[i], function (p, j) {
                      apply(j, me[i], pr[i]);
                    });
                  } else {
                    pr[i] = me[i];
                  }
                }
              }(j, me, v[1]));
              if (Util.isUndefined(w)) {
                delete me[j];
              } else if ((me[j] !== w) && (j !== 'constructor')) {
                me[j] = w;
              }
            });
          });
          return response;
        };
      }(i, v, parent.prototype)) : v;
    });
    classes[store_in][n] = function (o) {
      var me = this, _protected = {}, _private = {}, store,
        prototype = Util.getPrototype(me);
      if ('_public' === store_in) {
        store = store_in;
        //noinspection JSUnusedAssignment
        store_in = '_protected';
        _protected = window.Class.create(n, o);
        //noinspection JSUnusedAssignment
        store_in = '_private';
        _private = window.Class.create(n, o);
        store_in = store;
      }
      Util.each(prototype, function (v, i) {
        if (Util.isFunction(v)) {
          me[i] = (function (v) {
            return function (bag) {
              (bag instanceof ArgumentBag) || (bag = new ArgumentBag(_protected,
                _private, arguments));
              return v.call(this, bag);
            };
          }(v));
        }
      });
      if (!init) {
        me.options = o;
        Util.each(o, function (v, i) {
          !Util.isFunction(v) && !Util.isUndefined(me[i]) &&
            (me[i] = v);
        });
        Util.each(listeners[n], function (v, i) {
          Util.each(v, function (evt) {
            me.on(i, evt);
          });
        });
        Util.each(o.listeners || {}, function (v, i) {
          me.on(i, v);
        });
        me.init && me.init();
      }
    };
    classes[store_in][n].prototype = prototype;
    classes[store_in][n].prototype.constructor = classes[store_in][n];
  };
  window.Class.create = function (n, o) {
    if (!classes[store_in][n]) {
      throw 'FatalError: class "' + n + '" is not defined';
    }
    return new classes[store_in][n](o || {});
  };
  window.Class.is = function (n, o) {
    if (!classes[store_in][n]) {
      throw 'FatalError: class "' + n + '" is not defined';
    }
    return o instanceof classes[store_in][n];
  };
}());